#include "trace.h"
#include <iostream>
#include <algorithm>


using namespace std;


Trace::Trace(int threads,uint32_t pageSize){
        _table = new std::map<uint64_t,uint32_t>[threads];
        _mem = new std::vector<Access>[threads];
        _mutex = new std::vector<uint32_t>[threads];
        _memsort = new std::vector<Pageobj>[threads];
        _threads=threads;
        _pageSize=pageSize;
        _gAccesses=0;
        _skipwrite=false;
        _usePages=false;
        _region = new uint32_t[_threads];
        memset(_region,0,sizeof(uint32_t)*_threads);
        _threadIndex = new uint32_t[_threads];
        _iteration = new uint32_t[_threads];
        _traceIterations=0;
        _traceRegionLines = new uint32_t[_threads];
        memset(_threadIndex,0,sizeof(uint32_t)*_threads);
        memset(_traceRegionLines,0,sizeof(uint32_t)*_threads);
        memset(_iteration,0,sizeof(uint32_t)*_threads);
        memset(_dirname,0,sizeof(char)*1024);
        pthread_mutex_init(&_mutex1,NULL);

}
Trace::~Trace(){
    delete [] _table;
    delete [] _mem;
    delete [] _mutex;
    delete [] _memsort;
    delete [] _region;
    delete [] _threadIndex;
    delete [] _traceRegionLines;
    delete [] _iteration;
}

void
Trace::reset() {
    for (int i=0;i<_threads;i++) {
        _table[i].clear();
        _mem[i].clear();
        _memsort[i].clear();
        _mutex[i].clear();

    }
    memset(_region,0,sizeof(uint32_t)*_threads);
    memset(_threadIndex,0,sizeof(uint32_t)*_threads);
    memset(_traceRegionLines,0,sizeof(uint32_t)*_threads);
    memset(_iteration,0,sizeof(uint32_t)*_threads);
    //pthread_mutex_init(&_mutex1,NULL);
}

bool
Trace::has_suffix(const std::string& path, const std::string& suffix)
{
    return path.rfind(suffix) == (path.length() - suffix.length());
}

std::auto_ptr< std::istream >
Trace::create_istream(const std::string& path)
{
    std::auto_ptr< std::istream > is;
    if (has_suffix(path, ".gz"))
        is.reset(new cyclesim::utils::gzistream(path));
    else {
        std::auto_ptr< std::ifstream > ifs(new std::ifstream(path.c_str()));
        if (!*ifs)
            throw cyclesim::utils::not_found_error(path);
        is = ifs;
    }
    assert(is.get() != NULL);
    return is;
}

std::string
Trace::replaceRegionName(std::string region){
    size_t found =region.find_first_of("$");
    if (found!=string::npos) {
        region.replace(found,1,"X");
        //region.replace(found+1,1,"");
    }

    return region;

}
uint32_t
Trace::threadFromRegion(std::string region)
{
    size_t start =region.find("t:",0);
    size_t end =region.find(",",start);
    start+=2;
    if (end-start>0) {
        std::string t = region.substr(start,end);
        return atoi(t.c_str());
    }
    return 0;
}

void
Trace::getDirname(char * dir)
{
    strcpy(dir,_dirname);
}

void
Trace::setParams(const char *dirname,uint32_t gThreads,bool skipwrite,
                 bool usePages,uint32_t iterations)
{
    strcpy(_dirname,dirname);
    _threads=gThreads;
    _skipwrite=skipwrite;
    _usePages=usePages;
    _traceIterations=iterations;
}

int Trace::preProcess(uint32_t tid) {
    pthread_mutex_lock( &_mutex1 );

    if ( _region[tid]>=MAXREGION) {
        pthread_mutex_unlock( &_mutex1 );
        return 0;
    }

    if (tid>=_threads) {
        std::cout<< "Unable to handle thread identifier."<<std::endl;
        pthread_mutex_unlock( &_mutex1 );
        return 0;
    }

    using cyclesim::modules::cpus::ins::Component;
    using cyclesim::modules::cpus::ins::ComponentSet;
    using cyclesim::modules::cpus::ins::get_components;
    using cyclesim::modules::cpus::ins::trace_name;
    const std::string directory = _dirname;
    ComponentSet comps = get_components(directory,tid);
    // limit the number of files TO MAXREGION
    ComponentSet::const_iterator iter= comps.begin();

    for (int i=0;(i<_region[tid] && iter!=comps.end());i++) {
        iter++;
    }

    if (iter==comps.end()) {
        pthread_mutex_unlock( &_mutex1 );
        return 0;
    }
    const Component& c = *iter;
    const static std::string fmt = "gzip";
    const std::string rwefecdir_src =
        trace_name(directory, c.first, c.second, "efecdirrw", fmt);
    const std::string rwefecdir=replaceRegionName(rwefecdir_src);
    uint32_t tid2 = threadFromRegion(rwefecdir);
    std::cout << "FileInfo:"<<rwefecdir<<" --t:"<<tid<<"---"<<tid2<<std::endl;
    this->preProcessFile(rwefecdir,tid);

    pthread_mutex_unlock( &_mutex1 );
    return 1;
}
/*
 * Read Trace Directory and allocate memory for the whole application
 */
int Trace::preProcessFile(const std::string rwefecdir,const uint32_t tid) {
   std::string buf="";
   char op='R';
   uint64_t address=0;

    // Skip  files not found due naming convention
   try {
       std::auto_ptr< std::istream > is = create_istream(rwefecdir);

       // Advance pre-buffer file pointer
       if (_traceRegionLines[tid]!=0)
           is->seekg(_traceRegionLines[tid], ios::beg);

       while (is->good() && _mem[tid].size()<MAX_TRACE_BUFFERING) {
           try {
               if (std::getline(*is, buf).good()) {
                   std::istringstream reader(buf);
                   reader >> std::hex >>address;
                   // we read R/W
                   reader >> op;
                   // Store information in global dictionary
                   if (op=='R' || (op=='W' && !_skipwrite)) {
                       uint32_t memopcode=LOAD_INST;
                       memopcode=(op=='R'?LOAD_INST:STORE_INST);
                       if (_usePages)  address=address/_pageSize;
                       this->access(tid,address,memopcode);
                       _gAccesses++;
                   }
               }
           } catch (const std::runtime_error& e) {
               throw ParseError(std::string("Failed to read efecdir line: ") + e.what());
           }
       }
       // finished reading file means MUTEX
       if (!is->good()) {
           _region[tid]++;
           _traceRegionLines[tid]=0;
           this->setMutex(tid);
       }
       else {
           _traceRegionLines[tid]= is->tellg();
       }
   }
   catch (const std::runtime_error& e) {
       std::cout <<"Not found:" << e.what()<<std::endl;
       exit(1);
   }
   return 1;
}
bool Trace::access(uint32_t thread,uint64_t page,uint32_t memOpcode) {
    std::map<uint64_t,uint32_t>::iterator iter;

    Access item ={page,memOpcode};
    _mem[thread].push_back(item);

    iter=_table[thread].find(page);
    if (iter==_table[thread].end()) {
        std::pair<uint64_t,uint32_t> data(page,1);
        _table[thread].insert(data);
        return false;
     }
     else{
        (uint32_t)(iter->second)++;
        return true;
     }
}

void
Trace::setMutex(uint32_t thread)
{
    if (thread>=_threads)
        return;

    _mutex[thread].push_back(_mem[thread].size());
}

bool
Trace::isMutex(uint32_t thread,uint32_t numaccess)
{
    std::vector<uint32_t>::iterator iter;
    for (iter= _mutex[thread].begin();iter< _mutex[thread].end();iter++)
    {
        if (*iter>numaccess) return false;
        if (*iter==numaccess) return true;
    }
    return false;
}

uint32_t
Trace::startNextTraceIteration(uint32_t tid)
{
    _iteration[tid]++;
    if (_iteration[tid]>=_traceIterations)
        return 0;
    else {
        _region[tid]=0;
        return 1;
    }
}

uint32_t
Trace::getPageAccess(uint32_t thread,uint32_t index)
{
        if (thread>_threads)
            return 0;
        /* threadIndex hold partial total  while mem is partial */
        index-=_threadIndex[thread];
        if (index>=_mem[thread].size())  {
            /* read next file */
            _mem[thread].clear();
            _threadIndex[thread]+=index;
            if (!preProcess(thread)) {
                if (!startNextTraceIteration(thread))
                    return 0;
                else
                    preProcess(thread);
            }
            index=0;
        }
        return  _mem[thread][index].address/_pageSize;
}

bool
Trace::getMemoryAccess(uint32_t thread,uint32_t index,uint64_t &address,
        uint32_t &memOpcode){

        if (thread>_threads)
            return false;

        index-=_threadIndex[thread];
        //std::cout<<"thread "<<thread<<" Index "<<index<<" mem thread "<<_mem[thread].size()<<std::endl;
        if (index>=_mem[thread].size())  {
            /* read next file */
            _mem[thread].clear();
            _threadIndex[thread]+=index;
            if (!preProcess(thread)) {
                if (!startNextTraceIteration(thread))
                    return false;
                else
                    preProcess(thread);
            }
            index=0;
        }

        //std::cout<<"thread "<<thread<<" Index "<<index<<std::endl;
        if (_mem[thread].size()>index) {
            address   =  _mem[thread][index].address;
            memOpcode =  _mem[thread][index].memOpcode;
            return true;
        }
        return false;
}

uint32_t
Trace::getAccessSize(uint32_t thread)
{
        return  _mem[thread].size();
}

uint64_t
Trace::getAccess(uint32_t thread,uint64_t page)
{
    std::map<uint64_t,uint32_t>::iterator iter;
     iter=_table[thread].find(page);
     if (iter==_table[thread].end()) {
        return 0;
     }
     else{
        return (uint32_t)(iter->second);
     }
}


void Trace::print(char * benchmark,ofstream &outfile,bool pagesmap){
    int i;
    std::map<uint64_t,uint32_t> pages;
    std::map<uint64_t,uint32_t>::iterator iter,iter2;
    char filename[128];

    //cout<<"Bench-"<<benchmark<<endl;

    // OUTPUT FILE OF PagesMap for each threads
    if (pagesmap) {
        for (i=0;i<_threads;i++){
            for(iter=_table[i].begin();iter!=_table[i].end();iter++) {
                    iter2=pages.find(iter->first);
                    if (iter2!=pages.end()) {
                        iter2->second++;
                    }
                    else {
                        std::pair<uint64_t,uint32_t> data(iter->first,iter->second+1);
                        pages.insert(data);
                    }
            }
        }
        ofstream outfile2;
        strcpy(filename,benchmark);
        strcat(filename,".pages");
        outfile2.open(filename);
        for(iter2=pages.begin();iter2!=pages.end();iter2++) {
            outfile2 <<iter2->first<< ","<<iter2->second<<endl;
        }
        outfile2.close();
    }
    for (i=0;i<_threads;i++) {
        cout <<"AccessedPagesxThread,"<<i<< ","<<_table[i].size()<<endl;
    }
}

bool mysort (Pageobj i,Pageobj j) { return (i._access>j._access); }

void Trace::sortMap() {
    std::map <uint64_t,uint32_t>::iterator it;

    for (int i=0;i<_threads;i++) {
        for (it=_table[i].begin();it!=_table[i].end();it++){
            Pageobj tmp((uint32_t)it->first,(uint32_t)it->second);
            _memsort[i].push_back(tmp);
        }
    }
    // Sort mem
    for (int i=0;i<_threads;i++) {
            sort(_memsort[i].begin(), _memsort[i].end(),mysort);
    }
}

uint32_t Trace::vectorthreadSize(uint32_t thread){
    if (thread>=_threads) return 0;
    else                  return _memsort[thread].size();
}

uint32_t Trace::vectorthreadPage(uint32_t thread,uint32_t index){
    if (thread>_threads)               return 0;
    if (index>_memsort[thread].size()) return 0;
    else                               return _memsort[thread][index]._access;
}

uint32_t Trace::getMaxAccess(uint32_t thread,uint32_t index){
    if (thread>_threads)                 return 0;
    if (index>_memsort[thread].size())   return 0;
    else                                 return _memsort[thread][index]._access;
}
uint32_t Trace::getMaxAccessPage(uint32_t thread,uint32_t index){
    if (thread>_threads)                 return 0;
    if (index>_memsort[thread].size())   return 0;
    else                                 return _memsort[thread][index]._page;
}

